package com.kizitonwose.calendarview.ui;

import ohos.agp.components.BaseItemProvider;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.LayoutScatter;
import ohos.agp.components.ListContainer;
import ohos.agp.utils.Rect;
import ohos.app.Context;

import com.kizitonwose.calendarview.CalendarView;
import com.kizitonwose.calendarview.bean.DayConfig;
import com.kizitonwose.calendarview.bean.ViewConfig;
import com.kizitonwose.calendarview.model.CalendarDay;
import com.kizitonwose.calendarview.model.CalendarMonth;
import com.kizitonwose.calendarview.model.MonthConfig;
import com.kizitonwose.calendarview.model.enums.DayOwner;
import com.kizitonwose.calendarview.model.enums.ScrollMode;
import com.kizitonwose.calendarview.uinterface.MonthHeaderFooterBinder;

import java.lang.reflect.InvocationTargetException;
import java.time.LocalDate;
import java.time.YearMonth;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;

/**
 * CalendarAdapter
 *
 * @version 1.0
 * @since 2021-02-26
 */
public class CalendarAdapter extends BaseItemProvider {
    private static final String TAG = "CalendarAdapter";
    private static final int NO_INDEX = -1;
    private List<CalendarMonth> months;
    private CalendarView calView;
    private ViewConfig viewConfig;

    // 创造两个ID 两个ID不能与其他组件冲突
    int headerViewId = 0x012312311;
    int footerViewId = 0x012312312;
    private Context context;
    private MonthConfig monthConfig;

    /**
     * CalendarAdapter
     *
     * @param calView CalendarView
     * @param viewConfig ViewConfig
     * @param monthConfig MonthConfig
     * @param context Context
     */
    public CalendarAdapter(CalendarView calView, ViewConfig viewConfig, MonthConfig monthConfig, Context context) {
        this.months = monthConfig.getMonths();
        this.calView = calView;
        this.viewConfig = viewConfig;
        this.monthConfig = monthConfig;
        this.context = context;
    }

    @Override
    public int getCount() {
        return months.size();
    }

    @Override
    public CalendarMonth getItem(int i) {
        return months.get(i);
    }

    @Override
    public long getItemId(int i) {
        return getItem(i).hashCode();
    }

    @Override
    public Component getComponent(int position, Component component, ComponentContainer componentContainer) {
        MonthViewHolder monthViewHolder = null;
        final Component myComponent;
        if (component == null) {
            DirectionalLayout rootLayout = new DirectionalLayout(context);
            rootLayout.setOrientation(DirectionalLayout.VERTICAL);
            if (viewConfig.getMonthHeaderRes() != 0) {
                Component monthHeaderComp = LayoutScatter.getInstance(context).parse(viewConfig.getMonthHeaderRes(), null, false);
                if (monthHeaderComp.getId() == Component.ID_DEFAULT) {
                    monthHeaderComp.setId(headerViewId);
                } else {
                    headerViewId = monthHeaderComp.getId();
                }
                rootLayout.addComponent(monthHeaderComp);
            }

            DayConfig dayConfig = new DayConfig(calView.getDaySize(), viewConfig.getDayViewRes(), calView.getDayBinder());
            List<WeekHolder> weekHolders = new ArrayList();
            for (int i = 0; i < 6; i++) {
                WeekHolder weekHolder = new WeekHolder(createDayHolders(dayConfig));
                rootLayout.addComponent(weekHolder.inflateWeekView(rootLayout));
                weekHolders.add(weekHolder);
            }

            if (viewConfig.getMonthFooterRes() != 0) {
                Component monthFooterComponent = LayoutScatter.getInstance(rootLayout.getContext())
                    .parse(viewConfig.getMonthFooterRes(), null, false);
                if (monthFooterComponent.getId() == Component.ID_DEFAULT) {
                    monthFooterComponent.setId(footerViewId);
                } else {
                    footerViewId = monthFooterComponent.getId();
                }
                rootLayout.addComponent(monthFooterComponent);
            }
            MonthHeaderFooterBinder<ViewContainer> monthHeaderBinder = calView.getMonthHeaderBinder();
            MonthHeaderFooterBinder<ViewContainer> monthFooterBinder = calView.getMonthFooterBinder();
            component = getUserRoot(context, rootLayout);
            monthViewHolder = new MonthViewHolder(this,
                (ComponentContainer) component,
                weekHolders,
                monthHeaderBinder,
                monthFooterBinder);

            component.setTag(monthViewHolder);
        } else {
            // 返回缓存的component时,需要对component的LayoutConfig进行重置(每个月的周行数会变化)
            setupRoot((ComponentContainer) component);
            monthViewHolder = (MonthViewHolder) component.getTag();
        }
        myComponent = component;

        monthViewHolder.bindMonth(getItem(position));

        return myComponent;
    }

    /**
     * 刷新单行
     *
     * @param listContainer ListContainer
     * @param position int
     * @param calendarDay CalendarDay
     */
    public void updateSingleRow(ListContainer listContainer, int position, CalendarDay calendarDay) {
        if (listContainer != null) {
            int firstVisibleItemPosition = listContainer.getFirstVisibleItemPosition();
            int lastVisibleItemPosition = listContainer.getLastVisibleItemPosition();
            if (position >= firstVisibleItemPosition && position <= lastVisibleItemPosition) {
                Component itemComponent = listContainer.getComponentAt(position);
                MonthViewHolder monthViewHolder = (MonthViewHolder) itemComponent.getTag();
                monthViewHolder.reloadDay(calendarDay);
            }
        }
    }

    /**
     * 获取内容提供者是否是本身
     *
     * @return IsAttached boolean
     */
    public boolean getIsAttached() {
        return calView.getItemProvider() == this;
    }

    /**
     * 创建DayHolders
     *
     * @param dayConfig DayConfig
     * @return List<DayHolder> DayHolder的集合
     */
    public List<DayHolder> createDayHolders(DayConfig dayConfig) {
        ArrayList<DayHolder> dayHolders = new ArrayList<>();
        for (int i = 0; i < 7; i++) {
            DayHolder dayHolder = new DayHolder(dayConfig);
            dayHolders.add(dayHolder);
        }
        return dayHolders;
    }

    /**
     * 设置根布局
     *
     * @param root ComponentContainer
     */
    public void setupRoot(ComponentContainer root) {

        root.setPaddingRelative(calView.getMonthPaddingStart(), calView.getMonthPaddingTop(),
            calView.getMonthPaddingEnd(), calView.getMonthPaddingBottom());
        int layoutWidth;
        if (calView.isAutoSize()) {
            layoutWidth = ComponentContainer.LayoutConfig.MATCH_PARENT;
        } else {
            layoutWidth = ComponentContainer.LayoutConfig.MATCH_CONTENT;
        }
        ComponentContainer.LayoutConfig layoutConfig = new ComponentContainer.LayoutConfig(layoutWidth,
            ComponentContainer.LayoutConfig.MATCH_CONTENT);
        layoutConfig.setMarginsLeftAndRight(calView.getMonthMarginStart(), calView.getMonthMarginEnd());
        layoutConfig.setMarginsTopAndBottom(calView.getMonthMarginTop(), calView.getMonthMarginBottom());
        root.setLayoutConfig(layoutConfig);
    }

    /**
     * 获取根布局
     *
     * @param context Context
     * @param rootLayout ComponentContainer
     * @return ComponentContainer 根布局
     */
    public ComponentContainer getUserRoot(Context context, ComponentContainer rootLayout) {
        // viewConfig
        ComponentContainer componentContainer = null;
        try {
            if (viewConfig.getMonthViewClass() != null) {
                componentContainer = (ComponentContainer) Class.forName(viewConfig.getMonthViewClass()).
                    getDeclaredConstructor(Context.class).newInstance(context);
                componentContainer.addComponent(rootLayout);
            } else {
                componentContainer = rootLayout;
            }
            setupRoot(componentContainer);

        } catch (NoSuchMethodException | ClassNotFoundException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
            e.printStackTrace();
        }

        return componentContainer;
    }

    /**
     * 根据position获取CalendarMonth
     *
     * @param position int
     * @return CalendarMonth
     */
    public CalendarMonth getCalendarMonthItem(int position) {
        return months.get(position);
    }

    /**
     * 重新加载日
     *
     * @param day CalendarDay
     */
    public void reloadDay(CalendarDay day) {
        int position = getAdapterPosition(day);
        if (position != NO_INDEX) {
            notifyDataSetItemChanged(position);
        }
    }

    /**
     * 重新加载月份
     *
     * @param month YearMonth
     */
    public void reloadMonth(YearMonth month) {
        notifyDataSetItemChanged(getAdapterPosition(month));
    }

    /**
     * reloadCalendar 重新加载日历
     */
    public void reloadCalendar() {
        notifyDataSetItemRangeChanged(0, getCount());
    }

    private CalendarMonth visibleMonth = null;
    private Boolean calWrapsHeight = null;

    /**
     * notifyMonthScrollListenerIfNeeded 刷新数据
     */
    public void notifyMonthScrollListenerIfNeeded() {
        if (!getIsAttached()) {
            return;
        }

        if (calView.isAnimating()) {
            return;
        }
        List<CalendarMonth> calendarMonths = months;

        // 鸿蒙的scroll方法为异步刷新，后续操作如果涉及刷新后才能进行的操作需保证滑动结束再进行
        calView.getContext().getUITaskDispatcher().asyncDispatch(() -> {
            int visibleItemPos = findFirstVisibleMonthPosition();
            System.out.println("notifyMonthScrollListenerIfNeeded 当前可见的第一项序列为：" + visibleItemPos);
            if (visibleItemPos != ListContainer.INVALID_INDEX && visibleItemPos < calendarMonths.size()) {
                CalendarMonth visibleMonth = calendarMonths.get(visibleItemPos);
                if (!visibleMonth.equals(this.visibleMonth)) {
                    System.out.println("notifyMonthScrollListenerIfNeeded 当前可见的第一项发生变更");
                    this.visibleMonth = visibleMonth;
                    if (calView.getMonthScrollListener() != null) {
                        calView.getMonthScrollListener().onMonthScroll(visibleMonth);
                    }
                    if (calView.getScrollMode() == ScrollMode.PAGED) {
                        if (calWrapsHeight == null) {
                            calWrapsHeight = calView.getLayoutConfig().height == ComponentContainer.LayoutConfig.MATCH_CONTENT;
                        }
                        if (!calWrapsHeight) {
                            return;
                        }
                    }
                }
            }
        });
    }

    /**
     * 根据CalendarDay获取显示位置
     *
     * @param day CalendarDay
     * @return position int
     */
    public int getAdapterPosition(CalendarDay day) {
        if (monthConfig.isHasBoundaries()) {
            int firstMonthIndex = getAdapterPosition(day.getPositionYearMonth());
            if (firstMonthIndex == NO_INDEX) {
                return NO_INDEX;
            }
            CalendarMonth firstCalMonth = months.get(firstMonthIndex);
            List<CalendarMonth> sameMonths = months.subList(firstMonthIndex, firstMonthIndex + firstCalMonth.getNumberOfSameMonth());
            int indexWithDateInSameMonth = -1;
            int size = sameMonths.size();
            sameMonths:
            for (int index = 0; index < size; index++) {
                List<List<CalendarDay>> weekDays = sameMonths.get(index).getWeekDays();
                for (List<CalendarDay> weeks : weekDays) {
                    for (CalendarDay calendarDay : weeks) {
                        if (calendarDay.equals(day)) {
                            indexWithDateInSameMonth = index;
                            break sameMonths;
                        }
                    }
                }
            }

            if (indexWithDateInSameMonth == NO_INDEX) {
                return NO_INDEX;
            } else {
                return firstMonthIndex + indexWithDateInSameMonth;
            }
        } else {
            int indexWithDateInSameMonth = -1;
            int size = months.size();
            months:
            for (int index = 0; index < size; index++) {
                List<List<CalendarDay>> weekDays = months.get(index).getWeekDays();
                for (List<CalendarDay> weeks : weekDays) {
                    for (CalendarDay calendarDay : weeks) {
                        if (calendarDay.equals(day)) {
                            indexWithDateInSameMonth = index;
                            break months;
                        }
                    }
                }
            }
            return indexWithDateInSameMonth;
        }
    }

    /**
     * 左滑 拿最后一个可见月份并滑到该月份
     * 右滑 拿第一个可见月份并滑到该月份
     */
    public void scrollToNextPositionAuto() {
        if (calView.getScrollMode() == ScrollMode.CONTINUOUS) {
            return;
        }
    }

    /**
     * 根据LocalDate获取当前显示的position
     *
     * @param date LocalDate
     * @return position int
     */
    public int getAdapterPosition(LocalDate date) {
        return getAdapterPosition(new CalendarDay(date, DayOwner.THIS_MONTH));
    }

    /**
     * 根据YearMonth获取当前显示ID
     *
     * @param month YearMonth
     * @return index int
     */
    public int getAdapterPosition(YearMonth month) {
        int index = NO_INDEX;
        int size = months.size();
        for (int i = 0; i < size; i++) {
            if (months.get(i).getYearMonth().equals(month)) {
                index = i;
                break;
            }
        }

        return index;
    }

    /**
     * 获取第一个显示的月份
     *
     * @return CalendarMonth 日历的月
     */
    public CalendarMonth findFirstVisibleMonth() {
        int monthPosition = findFirstVisibleMonthPosition();
        if (monthPosition >= 0 && monthPosition < months.size()) {
            return months.get(monthPosition);
        }
        return null;
    }

    /**
     * 获取最后一个显示的月份
     *
     * @return CalendarMonth 日历的月
     */
    public CalendarMonth findLastVisibleMonth() {
        int monthPosition = findLastVisibleMonthPosition();
        if (monthPosition >= 0 && monthPosition < months.size()) {
            return months.get(monthPosition);
        }
        return null;
    }

    /**
     * 获取第一个显示的CalendarDay
     *
     * @return CalendarDay 日历的 天
     */
    public CalendarDay findFirstVisibleDay() {
        return findVisibleDay(true);
    }

    /**
     * 获取最后一个显示的CalendarDay
     *
     * @return CalendarDay 日历的 天
     */
    public CalendarDay findLastVisibleDay() {
        return findVisibleDay(false);
    }

    private CalendarDay findVisibleDay(boolean isFirst) {
        List<CalendarMonth> monthsLocal = months;
        int visibleIndex;
        CalendarDay calendarDay = null;
        if (isFirst) {
            visibleIndex = findFirstVisibleMonthPosition();
        } else {
            visibleIndex = findLastVisibleMonthPosition();
        }

        if (visibleIndex == NO_INDEX) {
            return null;
        }

        ComponentContainer visibleItemView = (ComponentContainer) calView.getComponentAt(visibleIndex);
        if (visibleItemView == null) {
            return null;
        }
        Rect monthRect = new Rect();
        visibleItemView.getSelfVisibleRect(monthRect);
        Rect dayRect = new Rect();
        List<List<CalendarDay>> lists = new ArrayList<>();
        try {
            lists = monthsLocal.get(visibleIndex).getWeekDays();
        } catch (Exception e) {
            e.printStackTrace();
        }

        List<CalendarDay> calendarDayList = new ArrayList<>();
        for (List<CalendarDay> calendarDays : lists) {
            for (CalendarDay mCalendarDay : calendarDays) {
                calendarDayList.add(mCalendarDay);
            }
        }
        int calendarSize = calendarDayList.size();
        if (isFirst) {
            for (int i = 0; i < calendarSize; i++) {
                CalendarDay calendarDay1 = calendarDayList.get(i);
                if (isTargetCalendar(visibleItemView, calendarDay1, dayRect, monthRect)) {
                    calendarDay = calendarDay1;
                    break;
                }
            }
        } else {
            for (int i = calendarSize - 1; i >= 0; i--) {
                CalendarDay calendarDay1 = calendarDayList.get(i);
                if (isTargetCalendar(visibleItemView, calendarDay1, dayRect, monthRect)) {
                    calendarDay = calendarDay1;
                    break;
                }
            }
        }
        return calendarDay;
    }

    /**
     * 非递归广度遍历,利用队列数据结构
     *
     * @param root Component
     * @param calendarDay CalendarDay
     * @param dayRect Rect
     * @param monthRect Rect
     * @return boolean isTargetCalendar
     */
    private boolean isTargetCalendar(Component root, CalendarDay calendarDay, Rect dayRect, Rect monthRect) {
        boolean isTarget = false;
        ArrayDeque queue = new ArrayDeque();
        Rect parentRect = monthRect;
        queue.addLast(root);
        while (!queue.isEmpty() && !isTarget) {
            // 取得队头
            Component front = (Component) queue.getFirst();
            // 如果为ComponentContainer则使子节点入队列
            if (front instanceof ComponentContainer) {
                // 先判断父容器
                if (isTheDayMatchView(front, calendarDay)) {
                    isTarget = isTheTargetDayComponent(front, dayRect, parentRect);
                } else {
                    // 在判断子view
                    int childCount = ((ComponentContainer) front).getChildCount();
                    for (int i = 0; i < childCount; i++) {
                        Component childComponent = ((ComponentContainer) front).getComponentAt(i);
                        queue.addLast(childComponent);
                        if (isTheDayMatchView(childComponent, calendarDay)) {
                            // 必须取得目标view的直接父布局的自身可见区域（父布局的可见区域又相对其父布局以此类推）
                            front.getSelfVisibleRect(parentRect);
                            isTarget = isTheTargetDayComponent(childComponent, dayRect, parentRect);
                            if (isTarget) {
                                break;
                            }
                        }
                    }
                }
            } else if (front != null) {
                if (isTheDayMatchView(front, calendarDay)) {
                    isTarget = isTheTargetDayComponent(front, dayRect, parentRect);
                }
            }
            queue.pollFirst();
        }
        return isTarget;
    }

    private boolean isTheTargetDayComponent(Component component, Rect dayRect, Rect monthRect) {
        // getSelfVisibleRect返回的总是自身相对其平级最近的控件的区域坐标
        component.getSelfVisibleRect(dayRect);

        // isIntersect  dayView的可见区域与父布局可见区域是否相交
        return dayRect.right != 0 && dayRect.bottom != 0 && dayRect.right <= monthRect.right && dayRect.bottom <= monthRect.bottom;
    }

    private boolean isTheDayMatchView(Component component, CalendarDay calendarDay) {
        if (component != null && calendarDay != null) {
            if (component.getTag() != null) {
                return component.getTag().equals(calendarDay.getDate().hashCode());
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    private int findFirstVisibleMonthPosition() {
        return findVisibleMonthPosition(true);
    }

    private int findLastVisibleMonthPosition() {
        return findVisibleMonthPosition(false);
    }

    private int findVisibleMonthPosition(boolean isFirst) {
        if (isFirst) {
            System.out.println("第一项可见的index为：" + calView.getItemPosByVisibleIndex(0));
            return calView.getItemPosByVisibleIndex(0);
        } else {
            System.out.println("最后一项可见的index为：" + calView.getItemPosByVisibleIndex(calView.getVisibleIndexCount() - 1));
            return calView.getItemPosByVisibleIndex(calView.getVisibleIndexCount() - 1);
        }
    }

    public List<CalendarMonth> getMonths() {
        return months;
    }

    /**
     * 设置 MonthConfig
     *
     * @param newConfig MonthConfig
     */
    public void setMonthConfig(MonthConfig newConfig) {
        this.monthConfig = newConfig;
        months = newConfig.getMonths();
    }

    // 设置 viewConfig
    public void setViewConfig(ViewConfig viewConfig) {
        this.viewConfig = viewConfig;
    }
}
